package com.example.module.system.service.captcha;

import cloud.tianai.captcha.application.ImageCaptchaApplication;
import cloud.tianai.captcha.application.vo.CaptchaResponse;
import cloud.tianai.captcha.application.vo.ImageCaptchaVO;
import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.common.response.ApiResponse;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.example.framework.common.exception.ServiceException;
import com.example.framework.redis.core.util.RedisUtils;
import com.example.module.system.controller.admin.captcha.vo.CaptchaVo;
import com.example.module.system.controller.admin.captcha.vo.TianaiCaptchaVO;
import com.example.module.system.dal.redis.RedisKeyConstants;
import com.example.module.system.enums.ErrorCodeConstants;
import com.example.module.system.util.CaptchaUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class CaptchaService {
    /**
     * 拼图验证码允许偏差
     **/
    private static final Integer ALLOW_DEVIATION = 3;

    private static final String IMG_CODE_PREFIX = "imageCode:";
    private static final String NUMBER_CODE_PREFIX = "numberCode:";

    @Resource
    private ImageCaptchaApplication imageCaptchaApplication;

    private static final Integer CAPTCHA_EXPIRE_SECONDS = 60;

    /**
     * 获取验证码拼图（生成的抠图和带抠图阴影的大图及抠图坐标）
     **/
    public CaptchaVo getImageCaptcha(CaptchaVo captcha) {
        // 获取画布的宽高
        int canvasWidth = captcha.getCanvasWidth();
        int canvasHeight = captcha.getCanvasHeight();
        // 获取阻塞块的宽高/半径
        int blockWidth = captcha.getBlockWidth();
        int blockHeight = captcha.getBlockHeight();
        int blockRadius = captcha.getBlockRadius();
        // 获取资源图
        BufferedImage canvasImage = CaptchaUtils.getBufferedImage(captcha.getPlace());
        // 调整原图到指定大小
        canvasImage = CaptchaUtils.imageResize(canvasImage, canvasWidth, canvasHeight);
        // 随机生成阻塞块坐标
        int blockX = RandomUtil.randomInt(blockWidth, canvasWidth - blockWidth - 9);
        int blockY = RandomUtil.randomInt(10, canvasHeight - blockHeight + 1);
        // 阻塞块
        BufferedImage blockImage = new BufferedImage(blockWidth, blockHeight, BufferedImage.TYPE_4BYTE_ABGR);
        // 新建的图像根据轮廓图颜色赋值，源图生成遮罩
        CaptchaUtils.cutByTemplate(canvasImage, blockImage, blockWidth, blockHeight, blockRadius, blockX, blockY);
        // 移动横坐标
        String nonceStr = UUID.randomUUID().toString();
        // 缓存
        RedisUtils.set(IMG_CODE_PREFIX + nonceStr, blockX, 5, TimeUnit.MINUTES);
        // 设置返回参数
        captcha.setNonceStr(nonceStr);
        captcha.setBlockY(blockY);
        captcha.setBlockSrc(CaptchaUtils.toBase64(blockImage, "png"));
        captcha.setCanvasSrc(CaptchaUtils.toBase64(canvasImage, "png"));
        return captcha;
    }

    public boolean checkImageCaptcha(@NotEmpty String uuidStr, @NotNull Integer code) {
        Integer cachedCode = RedisUtils.get(IMG_CODE_PREFIX + uuidStr, Integer.class);
        if (Objects.isNull(cachedCode)) {
            throw ServiceException.of(ErrorCodeConstants.CODE_EXPIRED);
        }
        // 根据移动距离判断验证是否成功
        if (Math.abs(cachedCode - code) > ALLOW_DEVIATION) {
            throw ServiceException.of(ErrorCodeConstants.JIGSAW_ERROR);
        }
        return true;
    }

    public void getNumberCaptcha(HttpServletResponse response) {
        LineCaptcha captcha = CaptchaUtil.createLineCaptcha(200, 50, 4, 10);
        // 设置背景颜色
        captcha.setBackground(new Color(249, 251, 220));
        // 生成四位验证码
        String code = RandomUtil.randomNumbers(4);
        Image image = captcha.createImage(code);
        response.setContentType("image/jpeg");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        String uuidStr = UUID.randomUUID().toString();
        log.info("生成验证码 uuidStr：{} ，code:{}", uuidStr, code);
        // 缓存
        RedisUtils.set(NUMBER_CODE_PREFIX + uuidStr, code, 5, TimeUnit.MINUTES);

        response.setHeader("verifyCodeUuid", uuidStr);

        try {
            BufferedImage bufferedImage = ImgUtil.castToBufferedImage(image, ImgUtil.IMAGE_TYPE_JPG);
            // 创建一个ByteArrayOutputStream，用于存储图片数据
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            // 写入图片数据到ByteArrayOutputStream
            ImageIO.write(bufferedImage, "jpeg", outputStream);
            // 输出
            IoUtil.write(response.getOutputStream(), true, outputStream.toByteArray());
        } catch (IOException e) {
            log.error("生成验证码失败", e);
            throw ServiceException.of(e.getMessage());
        }
    }

    public boolean checkNumberCaptcha(String uuidStr, String code) {
        String redisKey = NUMBER_CODE_PREFIX + uuidStr;
        String cachedCode = RedisUtils.getString(redisKey);
        if (ObjectUtil.isEmpty(cachedCode)) {
            throw ServiceException.of(ErrorCodeConstants.CODE_EXPIRED);
        }
        if (!cachedCode.equals(code)) {
            throw ServiceException.of(ErrorCodeConstants.CODE_ERROR);
        }
        RedisUtils.delete(redisKey);
        return true;
    }

    public CaptchaResponse<ImageCaptchaVO> createCaptcha(String type) {
        if ("RANDOM".equals(type)) {
            int i = ThreadLocalRandom.current().nextInt(0, 4);
            if (i == 0) {
                type = CaptchaTypeConstant.SLIDER;
            } else if (i == 1) {
                type = CaptchaTypeConstant.CONCAT;
            } else if (i == 2) {
                type = CaptchaTypeConstant.ROTATE;
            } else {
                type = CaptchaTypeConstant.WORD_IMAGE_CLICK;
            }
        }
        return imageCaptchaApplication.generateCaptcha(type);
    }

    public String checkCaptcha(TianaiCaptchaVO captchaVo) {
        ApiResponse<?> response = imageCaptchaApplication.matching(captchaVo.getId(), captchaVo.getData());
        if (!response.isSuccess()) {
            throw ServiceException.of(response.getCode(), response.getMsg());
        }
        String ticket = RandomUtil.randomString(32);
        RedisUtils.set(RedisKeyConstants.CAPTCHA_TICKET_PREFIX + captchaVo.getId(), ticket, CAPTCHA_EXPIRE_SECONDS, TimeUnit.SECONDS);
        return ticket;
    }
}
